package com.jprocms.module.cms.service.sysword;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jpro.framework.common.pojo.PageResult;
import com.jpro.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.jprocms.module.cms.controller.admin.sysword.vo.*;
import com.jprocms.module.cms.convert.sysword.SysWordConvert;
import com.jprocms.module.cms.dal.dataobject.sysword.SysWordDO;
import com.jprocms.module.cms.dal.mysql.sysword.SysWordMapper;
import com.jprocms.module.cms.enums.DictTypeConstants;
import com.jprocms.module.cms.util.CmsContextUtil;
import com.jprocms.module.cms.util.SimpleTrie;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.baomidou.mybatisplus.extension.toolkit.Db.saveBatch;
import static com.baomidou.mybatisplus.extension.toolkit.Db.updateBatchById;
import static com.jpro.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.jpro.framework.common.util.collection.CollectionUtils.getMaxValue;
import static com.jprocms.module.cms.enums.ErrorCodeConstants.*;

/**
 * 词汇管理 Service 实现类
 *
 * @author jprocms
 */
@Slf4j
@Service
@Validated
public class SysWordServiceImpl implements SysWordService {

    @Resource
    private SysWordMapper sysWordMapper;
    /**
     * 易错词列表缓存
     */
    @Getter
    private volatile List<SysWordDO> easyErrorWordCache = Collections.emptyList();
    /**
     * 搜索联想词列表缓存
     */
    @Getter
    private volatile List<SysWordDO> searchSuggestWordCache = Collections.emptyList();
    /**
     * 易错词的字典树，包含所有易错词
     */
    @Getter
    private volatile SimpleTrie defaultEasyErrorWordTrie = new SimpleTrie(Collections.emptySet());

    /**
     * 初始化缓存 易错词
     */
    @PostConstruct
    public void initLocalCache() {
        // 第一步：查询数据
        List<SysWordDO> easyErrorWords = sysWordMapper.selectList(SysWordDO::getDictType, DictTypeConstants.CMS_WORD_TYPE_EASY_ERROR);
        log.info("[initLocalCache][缓存易错词，数量为:{}]", easyErrorWords.size());

        // 第二步：构建缓存
        // 写入 easyErrorWordCache 缓存
        easyErrorWordCache = easyErrorWords;
        // 初始化默认的 defaultSensitiveWordTrie
        defaultEasyErrorWordTrie = new SimpleTrie(com.jpro.framework.common.util.collection.CollectionUtils.convertList(easyErrorWords, SysWordDO::getWord));

        List<SysWordDO> searchSuggestWords = sysWordMapper.selectList(SysWordDO::getDictType, DictTypeConstants.CMS_WORD_TYPE_SEARCH_SUGGEST);
        log.info("[initLocalCache][缓存搜索推荐词，数量为:{}]", searchSuggestWords.size());
        searchSuggestWordCache = searchSuggestWords;
    }

    /**
     * 通过定时任务轮询，刷新缓存 10分钟定时
     * <p>
     * 目的：多节点部署时，通过轮询”通知“所有节点，进行刷新
     */
    @Scheduled(initialDelay = 60, fixedRate = 600, timeUnit = TimeUnit.SECONDS)
    public void refreshLocalCache() {
        // 情况一：如果缓存里没有数据，则直接刷新缓存
        if (CollUtil.isEmpty(easyErrorWordCache)) {
            initLocalCache();
            return;
        }

        // 情况二，如果缓存里数据，则通过 updateTime 判断是否有数据变更，有变更则刷新缓存
        LocalDateTime maxTime = getMaxValue(easyErrorWordCache, SysWordDO::getUpdateTime);
        if (sysWordMapper.selectCountByUpdateTimeGt(maxTime) > 0) {
            initLocalCache();
        }
    }

    @Override
    public Long createSysWord(SysWordCreateReqVO createReqVO) {
        // 插入
        SysWordDO sysWord = SysWordConvert.INSTANCE.convert(createReqVO);
        sysWord.setSiteId(CmsContextUtil.getCurrSiteId());
        sysWordMapper.insert(sysWord);
        // 刷新缓存
        initLocalCache();
        // 返回
        return sysWord.getId();
    }

    @Override
    public void updateSysWord(SysWordUpdateReqVO updateReqVO) {
        // 校验存在
        validateSysWordExists(updateReqVO.getId());
        // 更新
        SysWordDO updateObj = SysWordConvert.INSTANCE.convert(updateReqVO);
        sysWordMapper.updateById(updateObj);
        // 刷新缓存
        initLocalCache();
    }

    @Override
    public void deleteSysWord(Long id) {
        // 校验存在
        validateSysWordExists(id);
        // 删除
        sysWordMapper.deleteById(id);
        // 刷新缓存
        initLocalCache();
    }

    private void validateSysWordExists(Long id) {
        if (sysWordMapper.selectById(id) == null) {
            throw exception(SYS_WORD_NOT_EXISTS);
        }
    }

    @Override
    public SysWordDO getSysWord(Long id) {
        return sysWordMapper.selectById(id);
    }

    @Override
    public List<SysWordDO> getSysWordList(Collection<Long> ids) {
        return sysWordMapper.selectBatchIds(ids);
    }

    @Override
    public PageResult<SysWordDO> getSysWordPage(SysWordPageReqVO pageReqVO) {
        return sysWordMapper.selectPage(pageReqVO);
    }

    @Override
    public List<SysWordDO> getSysWordList(SysWordExportReqVO exportReqVO) {
        return sysWordMapper.selectList(exportReqVO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class) // 添加事务，异常则回滚所有导入
    public SysWordImportRespVO importWordList(List<SysWordImportExcelVO> importWords, boolean isUpdateSupport, String dictType) {
        if (CollUtil.isEmpty(importWords)) {
            throw exception(SYS_WORD_IMPORT_LIST_IS_EMPTY);
        }
        SysWordImportRespVO respVO = SysWordImportRespVO.builder().createWords(new ArrayList<>())
                .updateWords(new ArrayList<>()).failureWords(new LinkedHashMap<>()).build();
        importWords.forEach(importWord -> {
            LambdaQueryWrapper wrapper = new LambdaQueryWrapperX<SysWordDO>()
                    .eqIfPresent(SysWordDO::getWord, importWord.getWord())
                    .eqIfPresent(SysWordDO::getDictType, dictType);
            // 判断如果不存在，在进行插入
            SysWordDO existWord = sysWordMapper.selectOneByNoError(wrapper);
            if (existWord == null) {
                sysWordMapper.insert(SysWordConvert.INSTANCE.convert2(importWord, dictType));
                respVO.getCreateWords().add(importWord.getWord());
                return;
            }
            // 如果存在，判断是否允许更新
            if (!isUpdateSupport) {
                respVO.getFailureWords().put(importWord.getWord(), SYS_WORD_EXISTS.getMsg());
                return;
            }
            SysWordDO updateUser = SysWordConvert.INSTANCE.convert(importWord);
            updateUser.setId(existWord.getId());
            sysWordMapper.updateById(updateUser);
            respVO.getUpdateWords().add(importWord.getWord());
        });
        return respVO;
    }

    @Override
    public void addSearchCount(List<String> searchWords, Long count, Long siteId) {
        if (CollectionUtils.isEmpty(searchWords)) {
            return;
        }
        LambdaQueryWrapper queryWrapper = new LambdaQueryWrapperX<SysWordDO>()
                .inIfPresent(SysWordDO::getWord, searchWords);

        List<SysWordDO> list = sysWordMapper.selectList(queryWrapper);
        Set<String> existWord = list.stream().map(SysWordDO::getWord).collect(Collectors.toSet());
        if (CollectionUtils.isNotEmpty(list)) {
            list.forEach(c -> c.setHitCount(c.getHitCount() + count));
            updateBatchById(list);
        }
        Set<String> newWords = searchWords.stream().filter(w -> !existWord.contains(w)).collect(Collectors.toSet());
        if (CollectionUtils.isNotEmpty(newWords)) {
            List<SysWordDO> SysWordDOs = newWords.stream().map(w -> {
                SysWordDO sw = new SysWordDO();
                sw.setHitCount(count);
                sw.setSiteId(siteId);
                sw.setWord(w);
                return sw;
            }).collect(Collectors.toList());
            saveBatch(SysWordDOs);
        }
    }


    @Override
    public List<String> validateText(String text) {
        return defaultEasyErrorWordTrie.validate(text);
    }

    @Override
    public List<SysWordDO> findEasyErrorWordText(String text) {
        final List<String> errors = validateText(text);
        if (CollUtil.isEmpty(errors)) {
            return null;
        }
        List<SysWordDO> easyErrorWords = new ArrayList<>();
        for (String error : errors) {
            easyErrorWords.addAll(sysWordMapper.selectList(SysWordDO::getDictType, DictTypeConstants.CMS_WORD_TYPE_EASY_ERROR, SysWordDO::getWord, error));
        }
        return easyErrorWords;
    }

    @Override
    public boolean isTextValid(String text) {
        return defaultEasyErrorWordTrie.isValid(text);
    }

    @Override
    public List<SysWordDO> findSearchSuggest(String text) {
        List<SysWordDO> searchSuggestWords = new ArrayList<>();
        //高频查询直接从缓存中查询，缓存定时更新
        if (!searchSuggestWordCache.isEmpty()) {
            searchSuggestWords = searchSuggestWordCache.stream().filter(c -> c.getWord().contains(text)).collect(Collectors.toList());
        }
        return searchSuggestWords;
    }
}
